# -*- coding: utf-8 -*-

import math
import cv2
import numpy as np
import utils


def threshold(img, mode=cv2.THRESH_BINARY_INV, block_size=25, c=10):
    """
    将图片转换为二值化
    :param img: 图片
    :param mode: 转化为二值化的类型
    :param block_size:
    :param c:
    :return: 返回二值化后的OpenCV图像
    """
    img_gray = img.copy()
    # 判断图片是否为彩色的
    if len(img_gray.shape) == 3:
        img_gray = cv2.cvtColor(img_gray, cv2.COLOR_RGB2GRAY)
    # 自适应二值化
    img_thresh = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, mode, block_size, c)
    if utils.__DEBUG:
        utils.show_gray_image(img_thresh)
    return img_thresh


def recognize_rows(img_thresh, row_precision=100):
    """
    从图片中识别横线
    :param img_thresh: 二值化图片
    :param row_precision: 行识别精度，数字越大识别出的横线越多。
    :return: 返回识别出横线后的OpenCV图片
    """
    img_copied = img_thresh.copy()
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (img_copied.shape[1]//row_precision, 1))
    img_eroded = cv2.erode(img_copied, kernel, iterations=1)
    img_dilate = cv2.dilate(img_eroded, kernel, iterations=1)
    if utils.__DEBUG:
        utils.show_gray_image(img_dilate)
    return img_dilate


def recognize_cols(img_thresh, col_precision=45):
    """
    从图片中识别竖线
    :param img_thresh: 二值化图片
    :param col_precision: 列识别精度，数字越大识别出的竖线越多。
    :return: 返回识别出竖线后的OpenCV图片
    """
    img_copied = img_thresh.copy()
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, int(img_copied.shape[0]//col_precision)))
    img_eroded = cv2.erode(img_copied, kernel, iterations=1)
    img_dilate = cv2.dilate(img_eroded, kernel, iterations=1)
    if utils.__DEBUG:
        utils.show_gray_image(img_dilate)
    return img_dilate


def correct_table(img_rgb):
    """
    从图片中提取表格区域并进行透视矫正
    :param img_rgb: RGB图片
    """
    img_copied = img_rgb.copy()

    # 转换为灰度
    gray_img = utils.to_gray(img_copied)

    # 中值滤波 过滤噪声，保留边缘信息
    gray_img = cv2.medianBlur(gray_img, 5) 

    # Canny算子求得图像边缘
    edges_img = cv2.Canny(gray_img, 50, 150)

    # 定义一个5×5的十字形状的结构元
    kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5))
    # 重复膨胀 迭代5次
    edges_img = cv2.dilate(edges_img, kernel, iterations=5)

    # 寻找轮廓
    contours, _ = cv2.findContours(edges_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 获取面积最大的contour
    cnt = max(contours, key=lambda cnt: cv2.contourArea(cnt))    

    # 多变形近似
    epsilon = 0.1 * cv2.arcLength(cnt, True)
    # 返回点集，格式与contours相同
    approx = cv2.approxPolyDP(cnt, epsilon, True)
    # 排序坐标点，左上、左下、右下、右上
    approx = np.reshape(approx, (-1, 2))
    approx = utils.order_points(approx)
    approx = np.reshape(approx, (-1, 1, 2))    

    # 计算原矩形宽度和高度
    rect_w = int(utils.calc_distance(approx[0][0], approx[-1][0]))
    rect_h = int(utils.calc_distance(approx[0][0], approx[1][0]))

    pts1 = np.float32(approx)
    pts2 = np.float32([[0, 0], [0, rect_h], [rect_w, rect_h], [rect_w, 0]])
    M = cv2.getPerspectiveTransform(pts1, pts2)
    dst_img = cv2.warpPerspective(img_copied, M, (rect_w, rect_h))

    return dst_img


def extract_table(img, min_area):
    """
    从图片中提取表格部分
    :param img: 图片
    :param min_area: 表格区域最小面积
    :return: 返回表格部分的OpenCV图片
    """
    img_copied = img.copy()
    # 图片二值化
    img_thresh = threshold(img_copied)

    # 提取矩形框
    contours, _ = cv2.findContours(img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    outer_bound = utils.get_outer_contour(contours, min_area)
    if outer_bound is None:
        return img
    
    x, y, w, h = outer_bound

    if utils.__DEBUG:
        cv2.rectangle(img_copied, (x, y), (x+w, y+h), (0, 0, 255), 5)
        utils.save_image('output/bounded.jpg', img_copied)

    return img_copied[y:y+h, x:x+w]


def angle_correct(img, row_precision=150, min_line_length=200, max_line_gap=15):
    """
    图片角度校正
    :param img: 图片
    :param row_precision: 行识别精度，数字越大识别出的横线越多。
    :param min_line_length:
    :param max_line_gap:
    :return: 返回角度已经校正后的OpenCV图片
    """
    img_copied = img.copy()
    # 图片二值化
    img_thresh = threshold(img_copied)
    # 从二值化图片中识别横线
    img_lined = recognize_rows(img_thresh, row_precision)
    if utils.__DEBUG:
        utils.show_gray_image(img_lined)
    # 霍夫变换，提取横线坐标
    lines = cv2.HoughLinesP(img_lined, 1, np.pi / 180, 80, min_line_length, max_line_gap)
    # 将三维数组转换为(-1, 4)的二维数组
    lines = np.reshape(lines, (len(lines), 4))
    # 找到最长的直线
    angle = utils.get_correct_angle(lines, img_copied.shape[1]/3)
    # 旋转图片
    img_rotated = img_copied
    if angle != 0.0:
        img_rotated = utils.rotate(img_copied, angle)
    if utils.__DEBUG:
        print('angle=%f' % angle)
        utils.show_image(img_rotated)
    return img_rotated, angle


def cut_image_hist(projections, thresh, min_width = 20):
    """通过直方图裁剪图片
    :param projections 水平或者垂直的直方图
    :param thresh 限制阈值
    :param min_width 图片切割后应该达到的最小宽度
    :returns: 返回间隔索引
    """
    boundaries = []
    idx = None
    for i, val in enumerate(projections):
        if val >= thresh and idx is None:
            idx = i
        elif val < thresh and idx is not None:
            if i - idx > min_width:
                boundaries.append((idx, i))
                idx = None 
    return boundaries


def watershed(img_file_path):
    """
    使用分水岭算法提取数字连通区域
    :param img: 图像
    """
    img = cv2.imread(img_file_path)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
    # utils.show_gray_image(thresh)

    # 使用形态学运算滤除噪点
    kernel = np.ones((3,3),np.uint8)
    opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN, kernel, iterations=2)
    # utils.show_gray_image(opening)

    # 靠近目标中心的是前景 远离目标中心的是背景 硬币边缘是未知区域
    # 确定背景
    sure_bg = cv2.dilate(opening,kernel,iterations=3)
    # utils.show_gray_image(sure_bg)

    # 确定前景
    # 使用距离转换让硬币之间分开
    # 如果只是单纯抠前景 则可以不使用距离变换
    dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
    ret, sure_fg = cv2.threshold(dist_transform,0.75*dist_transform.max(),255,0)
    # utils.show_gray_image(sure_fg)

    # 确定未知区域
    # 背景图减去前景图得到未知区域
    # 类似于同心圆中大圆减去小圆得到圆环 圆环就是未知区域
    sure_fg = np.uint8(sure_fg)
    unknown = cv2.subtract(sure_bg,sure_fg)
    # utils.show_gray_image(unknown)

    # 从0开始进行标记
    # 这个函数可以将各个连通域从0开始标号 同一个连通域的像素的标签相同
    ret, markers = cv2.connectedComponents(sure_fg)

    # 因为0是未知区域 所有标签自增1
    markers = markers+1

    # 标记未知区域 这里unknown中的白色的环状区域为未知区域
    markers[unknown==255] = 0

    markers = cv2.watershed(img, markers)

    thresh[markers == -1] = 255
    thresh[markers != -1] = 0
    thresh = thresh[1:thresh.shape[0]-1,1:thresh.shape[1]-1]
    # utils.show_gray_image(thresh)

    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    return map(lambda cnt: cv2.boundingRect(cnt), list(contours))
    # for cnt in contours:
    #     x, y, w, h = cv2.boundingRect(cnt)
    #     cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 200), 2)
    # utils.show_image(img)


if __name__ == '__main__':
    utils.__DEBUG = True
    # img0 = utils.open_image('images/1.jpg')
    # img_thresh0 = threshold(img0)

    # 图片旋转
    # img1 = utils.open_image('images/4.jpg')
    # img_angled, angle1 = angle_correct(img1)

    img1 = utils.open_image('images/5.jpg')
    img_angled, angle1 = angle_correct(img1)
